/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.platformservice.persistence.basic;

import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDriver;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.pool.KeyedObjectPoolFactory;
import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.proteusframework.core.api.config.IConfiguration;
import org.proteusframework.core.api.model.INamespace;
import org.proteusframework.core.base.Namespace;
import org.proteusframework.core.util.Assert;
import org.proteusframework.platformservice.ServiceInitializationException;
import org.proteusframework.platformservice.grammar.IGrammarInspector;
import org.proteusframework.platformservice.grammar.IGrammarInspectorFactory;
import org.proteusframework.platformservice.grammar.IllegalExpressionException;
import org.proteusframework.platformservice.persistence.api.IPersistenceService;
import org.proteusframework.platformservice.persistence.api.IProjectManager;
import org.proteusframework.platformservice.persistence.api.IResultSet;
import org.proteusframework.platformservice.persistence.api.messagebean.IMessageBeanDescriptor;
import org.proteusframework.platformservice.persistence.api.messagebean.IMessageBeanDescriptorVisitor;
import org.proteusframework.platformservice.persistence.api.messagebean.IMessageBeanProperty;
import org.proteusframework.platformservice.persistence.base.AbstractMessageBeanManager;
import org.proteusframework.platformservice.persistence.basic.model.MessageBeanMetadataTable;
import org.proteusframework.platformservice.persistence.basic.model.MessageBeanPropertyTable;
import org.proteusframework.platformservice.persistence.basic.model.MessageBeanTable;
import org.proteusframework.platformservice.persistence.derivative.DerivativeFactory;
import org.proteusframework.platformservice.persistence.derivative.IDerivativeConverter;
import org.proteusframework.platformservice.persistence.messagebean.DataType;
import org.proteusframework.platformservice.persistence.messagebean.MessageBeanBuilder;
import org.proteusframework.platformservice.persistence.messagebean.ResultSetWrapper;
import org.proteusframework.platformservice.persistence.messagebean.UnsupportedMessageBeanException;
import org.proteusframework.platformservice.persistence.util.BeanUtil;

import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.proteusframework.platformservice.persistence.basic.DefaultPersistenceService.KEY_DB_CONFIG;

/**
 * NOTE: Turn on CONFIG level logging to see SQL statements.
 *
 * @author Tacoma Four
 */
public class DefaultMessageBeanManager extends AbstractMessageBeanManager
{
    private static final Logger logger = Logger.getLogger(DefaultMessageBeanManager.class.getCanonicalName());

    public static final String DERBY_CONFIG_RESOURCE_PATH = "/org/proteusframework/platformservice/persistence/basic/derby.properties";

    private static final String DEFAULT_PERSISTENCE = "default-persistence";

    public static final String JDBC_APACHE_COMMONS_DBCP_URL = "jdbc:apache:commons:dbcp:" + DEFAULT_PERSISTENCE;

    private static final String DDL_VERIFICATION_SQL_KEY = "ddl.verification.query";
    private static final String DDL_SCRIPT_NAME_KEY = "ddl.script.name";

    private static final String DML_QUERY_MAPPED_BEAN_EXISTS_KEY = "dml.query.mapped.bean.exists";
    private static final String DML_QUERY_MAPPED_BEAN_KEY = "dml.query.mapped.bean";
    private static final String DML_QUERY_MAPPED_BEAN_PROPERTIES_KEY = "dml.query.mapped.bean.properties";
    private static final String DML_QUERY_MAPPED_BEAN_METADATA_KEY = "dml.query.mapped.bean.metadata";

    private static final String DML_INSERT_MAPPED_BEAN_KEY = "dml.insert.mapped.bean";
    private static final String DML_INSERT_MAPPED_BEAN_PROPERTY_KEY = "dml.insert.mapped.bean.property";
    private static final String DML_INSERT_MAPPED_BEAN_METADATA_KEY = "dml.insert.mapped.bean.metadata";

    private String DDL_SCRIPT_NAME;
    private String DDL_VERIFICATION_QUERY;

    private String QUERY_MAPPED_BEAN_EXISTS;
    private String QUERY_MAPPED_BEAN;
    private String QUERY_MAPPED_BEAN_PROPERTIES;
    private String QUERY_MAPPED_BEAN_METADATA;

    private String INSERT_MAPPED_BEAN;
    private String INSERT_MAPPED_BEAN_PROPERTY;
    private String INSERT_MAPPED_BEAN_METADATA;

    private Deque<IMessageBeanDescriptor> pendingDescriptors = new ArrayDeque<IMessageBeanDescriptor>();

    /**
     * Lock used to synchronize queued bean registration and registration processing because the ArrayDeque is
     * NOT a thread safe data structure.
     */
    private final Object lock = new Object();
    private final SortedSet<IMessageBeanDescriptor> registeredDescriptors = new TreeSet<IMessageBeanDescriptor>();

    private final List<DataType> supportedDataTypes = new ArrayList<DataType>();

    private Map<DataType, String> sqlDataTypeMap = new HashMap<DataType, String>();

    private IProjectManager projectManager;


    @Override
    protected void onDelegateInjected()
    {
        projectManager = getDelegate().getPersistenceService().narrow(IProjectManager.class);

        String grammarInspectorFactory = getAppConfigService().getMetadata().getProperty(KEY_GRAMMAR_INSPECTOR_FACTORY);

        if (null == grammarInspectorFactory)
        {
            logger.config("No grammar inspector factory explicitly declared; default factory will be used");
        } else
        {


            try
            {
                logger.config("Instantiating IGrammarInspectorFactory from class " + grammarInspectorFactory);

                Class clazz = Class.forName(grammarInspectorFactory);

                if (null != clazz)
                {
                    if (IGrammarInspectorFactory.class.isAssignableFrom(clazz))
                    {
                        setGrammarInspectorFactory((IGrammarInspectorFactory) clazz.newInstance());
                        logger.config("IGrammarInspectorFactory setup successfully from class " + grammarInspectorFactory);
                    } else
                    {
                        logger.severe("IGrammarInspectorFactory class declared in IAppConfigService is not assignable from IGrammarInspectorFactory");
                    }
                } else
                {
                    logger.severe("Unable to locate class " + grammarInspectorFactory);
                }
            } catch (Exception e)
            {
                logger.severe("IGrammarInspectorFactory class declared in IAppConfigService could not be loaded: " + e.getMessage());
            }
        }

        IConfiguration serviceConfig = getDelegate().getServiceConfiguration(IPersistenceService.class);
        String dbConfigPath = serviceConfig.getConfigPropertyOrDefault(KEY_DB_CONFIG).toString();

        InputStream sqlStream = this.getClass().getResourceAsStream(dbConfigPath);
        Assert.isNotNull(sqlStream);

        Properties sqlStatements = new Properties();
        try
        {
            sqlStatements.load(sqlStream);
        } catch (IOException e)
        {
            String msg = "Unable to load DB SQL statements from " + dbConfigPath + ": " + e.getMessage();
            logger.severe(msg);
            throw new IllegalStateException(msg, e);
        }

        supportedDataTypes.add(DataType.IdentityType);
        supportedDataTypes.add(DataType.CharType);
        supportedDataTypes.add(DataType.CharArrayType);
        supportedDataTypes.add(DataType.StringType);
        supportedDataTypes.add(DataType.IntegerType);
        supportedDataTypes.add(DataType.ShortType);
        supportedDataTypes.add(DataType.ByteType);
        supportedDataTypes.add(DataType.LongType);
        supportedDataTypes.add(DataType.FloatType);
        supportedDataTypes.add(DataType.DateType);
        supportedDataTypes.add(DataType.DoubleType);
        supportedDataTypes.add(DataType.NamespaceType);
        supportedDataTypes.add(DataType.BooleanType);
        supportedDataTypes.add(DataType.URLType);
        supportedDataTypes.add(DataType.UUIDType);
        supportedDataTypes.add(DataType.TimestampType);
        supportedDataTypes.add(DataType.TimeType);
        supportedDataTypes.add(DataType.ByteArrayType);
        supportedDataTypes.add(DataType.SerializableType);
        supportedDataTypes.add(DataType.BigDecimalType);
        supportedDataTypes.add(DataType.BigIntegerType);
        supportedDataTypes.add(DataType.TransientType);

        supportedDataTypes.add(DataType.BooleanArrayType);
        supportedDataTypes.add(DataType.ShortArrayType);
        supportedDataTypes.add(DataType.IntegerArrayType);
        supportedDataTypes.add(DataType.LongArrayType);
        supportedDataTypes.add(DataType.FloatArrayType);
        supportedDataTypes.add(DataType.DoubleArrayType);

        supportedDataTypes.add(DataType.EnumType);
        supportedDataTypes.add(DataType.StringArrayType);
        supportedDataTypes.add(DataType.DateArrayType);
        supportedDataTypes.add(DataType.NamespaceArrayType);

        supportedDataTypes.add(DataType.XmlType);

        DDL_VERIFICATION_QUERY = sqlStatements.getProperty(DDL_VERIFICATION_SQL_KEY);
        DDL_SCRIPT_NAME = sqlStatements.getProperty(DDL_SCRIPT_NAME_KEY);

        Assert.isNotNull(DDL_VERIFICATION_QUERY);
        Assert.isNotNull(DDL_SCRIPT_NAME);

        QUERY_MAPPED_BEAN = sqlStatements.getProperty(DML_QUERY_MAPPED_BEAN_KEY);
        QUERY_MAPPED_BEAN_PROPERTIES = sqlStatements.getProperty(DML_QUERY_MAPPED_BEAN_PROPERTIES_KEY);
        QUERY_MAPPED_BEAN_EXISTS = sqlStatements.getProperty(DML_QUERY_MAPPED_BEAN_EXISTS_KEY);
        QUERY_MAPPED_BEAN_METADATA = sqlStatements.getProperty(DML_QUERY_MAPPED_BEAN_METADATA_KEY);

        Assert.isNotNull(QUERY_MAPPED_BEAN);
        Assert.isNotNull(QUERY_MAPPED_BEAN_PROPERTIES);
        Assert.isNotNull(QUERY_MAPPED_BEAN_EXISTS);
        Assert.isNotNull(QUERY_MAPPED_BEAN_METADATA);

        INSERT_MAPPED_BEAN = sqlStatements.getProperty(DML_INSERT_MAPPED_BEAN_KEY);
        INSERT_MAPPED_BEAN_PROPERTY = sqlStatements.getProperty(DML_INSERT_MAPPED_BEAN_PROPERTY_KEY);
        INSERT_MAPPED_BEAN_METADATA = sqlStatements.getProperty(DML_INSERT_MAPPED_BEAN_METADATA_KEY);

        Assert.isNotNull(INSERT_MAPPED_BEAN);
        Assert.isNotNull(INSERT_MAPPED_BEAN_PROPERTY);
        Assert.isNotNull(INSERT_MAPPED_BEAN_METADATA);

        for (DataType type : supportedDataTypes)
        {
            String sql = sqlStatements.getProperty(type.name());
            sqlDataTypeMap.put(type, sql);
        }
    }


    @Override
    public SortedSet<IMessageBeanDescriptor> getMessageBeanDescriptors()
    {
        return registeredDescriptors;
    }

    private void executeSqlScript(String script)
    {
        Connection conn = null;
        Statement stmt = null;
        try
        {
            conn = DriverManager.getConnection(JDBC_APACHE_COMMONS_DBCP_URL);

            stmt = conn.createStatement();

            String[] sqlStatements = script.split(";");

            for (String sqlStatement : sqlStatements)
            {
                try
                {
                    logger.config("Executing SQL:\n\n" + sqlStatement + "\n\n");

                    stmt.execute(sqlStatement);
                } catch (Exception e)
                {
                    logger.severe("Unable to execute SQL (" + e.getMessage() + "): " + sqlStatement);
                    e.printStackTrace();
                }
            }

        } catch (Exception e)
        {
            logger.severe("Unable to execute script: " + e.getMessage());
            e.printStackTrace();
        } finally
        {
            try
            {
                DbUtils.close(stmt);
                DbUtils.close(conn);
            } catch (SQLException e)
            {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void processRegistrationQueue()
    {
        Connection conn = null;
        Statement stmt = null;
        PreparedStatement creatingMapping = null;
        PreparedStatement checkMapping = null;
        PreparedStatement propertyMapping = null;
        PreparedStatement metadataMapping = null;

        try
        {
            conn = DriverManager.getConnection(JDBC_APACHE_COMMONS_DBCP_URL);

            stmt = conn.createStatement();

            boolean requiresDDLExectuion = true;
            ResultSet rs = null;
            try
            {
                rs = stmt.executeQuery(DDL_VERIFICATION_QUERY);
                requiresDDLExectuion = !rs.next();

                logger.config("DefaultPersistenceService message bean management tables already exist- no change to DB");
            } catch (Exception exp)
            {
                // Ignore this- it simply means the table didn't exist and we need to build the scheme
                logger.config("Creating DefaultPersistenceService message bean management tables in a new project database...");
            } finally
            {
                DbUtils.close(rs);
            }

            if (requiresDDLExectuion)
            {
                InputStream is = this.getClass().getResourceAsStream(DDL_SCRIPT_NAME);

                BufferedReader reader = new BufferedReader(new InputStreamReader(is));

                StringBuilder sql = new StringBuilder();

                String line;
                while ((line = reader.readLine()) != null)
                {
                    sql.append(line);
                }

                if (logger.isLoggable(Level.FINE))
                {
                    logger.fine("SQL:\n\n" + sql.toString() + "\n\n");
                }

                String[] ddlStatements = sql.toString().split(";");

                for (String ddl : ddlStatements)
                {
                    try
                    {
                        stmt.execute(ddl);
                    } catch (Exception ddlExp)
                    {
                        ddlExp.printStackTrace();
                    }
                }
            }

            logger.config("Message bean database now online");

            creatingMapping = conn.prepareStatement(INSERT_MAPPED_BEAN, Statement.RETURN_GENERATED_KEYS);
            checkMapping = conn.prepareStatement(QUERY_MAPPED_BEAN_EXISTS);
            propertyMapping = conn.prepareStatement(INSERT_MAPPED_BEAN_PROPERTY);
            metadataMapping = conn.prepareStatement(INSERT_MAPPED_BEAN_METADATA);


            if (logger.isLoggable(Level.FINE))
            {
                logger.fine("Total message bean descriptors to register: " + registeredDescriptors.size());
            }

            synchronized (lock)
            {
                while (!pendingDescriptors.isEmpty())
                {
                    IMessageBeanDescriptor messageBeanDescriptor = pendingDescriptors.pop();
                    registeredDescriptors.add(messageBeanDescriptor);

                    String messageFamily = messageBeanDescriptor.getFamily();
                    String messageId = messageBeanDescriptor.getId();
                    String messageRefId = messageBeanDescriptor.getRefId();
                    String description = messageBeanDescriptor.getDescription();

                    logger.config("Attempting to register message bean " + Namespace.toCanonical(messageBeanDescriptor));

                    boolean alreadyMapped = false;
                    ResultSet mapRs = null;
                    try
                    {
                        checkMapping.clearParameters();

                        checkMapping.setString(1, messageFamily);
                        checkMapping.setString(2, messageId);

                        mapRs = checkMapping.executeQuery();

                        if (mapRs.next())
                        {
                            alreadyMapped = mapRs.getInt(1) > 0;
                        }

                    } catch (Exception exp)
                    {
                        exp.printStackTrace();
                    } finally
                    {
                        DbUtils.close(mapRs);
                    }


                    if (!alreadyMapped)
                    {
                        try
                        {
                            creatingMapping.clearParameters();

                            creatingMapping.setString(1, messageFamily);
                            creatingMapping.setString(2, messageId);
                            creatingMapping.setString(3, messageRefId);
                            creatingMapping.setString(4, description);

                            creatingMapping.executeUpdate();

                            if (logger.isLoggable(Level.CONFIG))
                            {
                                logger.config("Insert mapped Bean " + messageFamily + " / " + messageId);
                            }


                            long key = -1;
                            ResultSet keySet = creatingMapping.getGeneratedKeys();
                            if (keySet.next())
                            {
                                key = keySet.getLong(1);
                                if (logger.isLoggable(Level.FINE))
                                {
                                    logger.fine("Generated Key: " + key);
                                }
                            }

                            for (IMessageBeanProperty property : messageBeanDescriptor.properties())
                            {
                                propertyMapping.clearParameters();
                                propertyMapping.setLong(1, key);
                                propertyMapping.setString(2, property.getId());
                                propertyMapping.setString(3, property.getDataType().name());
                                propertyMapping.setInt(4, property.getLength());
                                propertyMapping.setInt(5, property.getPrecision());
                                propertyMapping.setBoolean(6, property.isNullable());
                                propertyMapping.setBoolean(7, property.isPrimaryKeyMember());
                                propertyMapping.setString(8, property.getDescription());
                                propertyMapping.executeUpdate();

                                if (logger.isLoggable(Level.CONFIG))
                                {
                                    logger.config("Mapped property " + property.getId());
                                }
                            }

                            Properties props = messageBeanDescriptor.getMetadata();
                            for (Object propKey : props.keySet())
                            {
                                metadataMapping.clearParameters();
                                metadataMapping.setLong(1, key);
                                metadataMapping.setString(2, propKey.toString());
                                metadataMapping.setString(3, props.getProperty(propKey.toString()));
                                metadataMapping.executeUpdate();

                                if (logger.isLoggable(Level.CONFIG))
                                {
                                    logger.config("Mapped metadata property " + propKey.toString());
                                }
                            }


                            createMessageBeanTable(messageBeanDescriptor);

                        } catch (SQLException e)
                        {
                            e.printStackTrace();
                        }

                        logger.config("Registered message bean " + Namespace.toCanonical(messageBeanDescriptor));

                    } else
                    {
                        if (logger.isLoggable(Level.CONFIG))
                        {
                            logger.config("Mapped bean " + Namespace.toCanonical(new Namespace(messageFamily, messageId)) + " verified as existing within the message store");
                        }
                    }
                }
            }

        } catch (IOException e)
        {
            final String msg = "Unable to locate " + DDL_SCRIPT_NAME + " to create DefaultPersistenceService message bean management tables";
            logger.severe(msg);
            throw new MissingResourceException(msg, DDL_SCRIPT_NAME, null);
        } catch (Exception e)
        {
            final String msg = "Unable to execute " + DDL_SCRIPT_NAME + " to create DefaultPersistenceService message bean management tables: " + e.getMessage();
            logger.severe(msg);
            e.printStackTrace();
            throw new IllegalStateException(msg + ": " + e.getMessage());
        } finally
        {
            try
            {
                DbUtils.close(stmt);
                DbUtils.close(checkMapping);
                DbUtils.close(creatingMapping);
                DbUtils.close(propertyMapping);
                DbUtils.close(metadataMapping);
                DbUtils.close(conn);
            } catch (SQLException e)
            {
                e.printStackTrace();
            }
        }
    }

    private void createMessageBeanTable(IMessageBeanDescriptor messageBeanDescriptor)
    {
        String tableName = extractTableName(messageBeanDescriptor);

        StringBuilder creationStatement = new StringBuilder("CREATE TABLE ")
                .append(tableName)
                .append(" (\n");


        List<String> pkList = new ArrayList<String>();

        int fieldCount = 0;

        Iterator<IMessageBeanProperty> propsIter = messageBeanDescriptor.properties().iterator();

        while (propsIter.hasNext())
        {
            IMessageBeanProperty property = propsIter.next();

            if (DataType.TransientType == property.getDataType())
            {
                continue;
            }

            String dataTypeSql = sqlDataTypeMap.get(property.getDataType());

            if (null != dataTypeSql)
            {
                if (fieldCount > 0)
                {
                    creationStatement.append(",\n");
                }

                int len = (property.getLength() == 0)
                        ? 255
                        : property.getLength();

                dataTypeSql = dataTypeSql.replace("%%LEN%%", String.valueOf(len));
                dataTypeSql = dataTypeSql.replace("%%PRECISION%%", String.valueOf(property.getPrecision()));

                creationStatement.append("\t")
                        .append(property.getId())
                        .append(" ")
                        .append(dataTypeSql)
                        .append(" ")
                        .append(!property.isNullable() ? "NOT NULL" : "");

                if (property.isPrimaryKeyMember())
                {
                    pkList.add(property.getId());
                }

                fieldCount++;

                if (!propsIter.hasNext())
                {
                    creationStatement.append("\n");
                }
            } else
            {
                logger.severe("Attempt to map a field to an unsupported data type: " + Namespace.toCanonical(property) + ": " + property.getDataType());
            }
        }

        creationStatement.append(");\n\n");

        creationStatement.append("ALTER TABLE ")
                .append(tableName)
                .append(" ADD CONSTRAINT PK_")
                .append(tableName)
                .append(" PRIMARY KEY (");

        Iterator<String> pkIter = pkList.iterator();
        while (pkIter.hasNext())
        {
            creationStatement.append(pkIter.next());
            if (pkIter.hasNext())
            {
                creationStatement.append(", ");
            }
        }
        creationStatement.append(");");

        logger.config("\n\n" + creationStatement.toString() + "\n\n");

        executeSqlScript(creationStatement.toString());

    }

    private String extractTableName(INamespace namespace)
    {
        return namespace.getFamily().replace('.', '_') + "_" + namespace.getId();
    }

    @Override
    public boolean supportsMessageBeanManagement()
    {
        return true;
    }

    @Override
    public boolean supportsExpressionQueries()
    {
        return true;
    }

    @Override
    public boolean supportsInterfaces()
    {
        return false;
    }

    @Override
    public boolean supportsIndices()
    {
        return false;
    }

    @Override
    public List<String> describeExpressionSupport()
    {
        return new ArrayList<String>();
    }

    @Override
    public List<DataType> describeDataTypeSupport()
    {
        return supportedDataTypes;
    }

    @Override
    public boolean isMessageBeanRegistered(IMessageBeanDescriptor messageBeanDescriptor)
    {
        return isMessageBeanRegistered((INamespace) messageBeanDescriptor);
    }

    @Override
    public void queueMessageBeanRegistration(IMessageBeanDescriptor messageBeanDescriptor) throws UnsupportedMessageBeanException
    {
        if (!projectManager.isProjectOpen())
        {
            throw new IllegalStateException("Message persistence is only valid within the context of an open project");
        }

        Assert.parameterNotNull(messageBeanDescriptor, "Parameter 'typeDescriptor' must not be null");


        synchronized (lock)
        {
            pendingDescriptors.push(messageBeanDescriptor);
        }

        logger.config("Queued message bean descriptor for registration: " + messageBeanDescriptor.toString());
    }

    @Override
    public boolean isMessageBeanRegistered(INamespace namespace)
    {
        Assert.parameterNotNull(namespace, "Parameter 'namespace' must not be null");

        boolean isRegistered = false;

        Connection conn = null;
        PreparedStatement query = null;
        ResultSet rs = null;
        try
        {

            conn = DriverManager.getConnection(JDBC_APACHE_COMMONS_DBCP_URL);

            query = conn.prepareStatement(QUERY_MAPPED_BEAN_EXISTS);
            query.setString(1, namespace.getFamily());
            query.setString(2, namespace.getId());

            rs = query.executeQuery();
            if (rs.next())
            {
                isRegistered = (rs.getInt(1) > 0);
            }

        } catch (SQLException ignored)
        {

        } finally
        {
            try
            {
                DbUtils.close(rs);
                DbUtils.close(query);
                DbUtils.close(conn);
            } catch (SQLException e)
            {
                e.printStackTrace();
            }
        }

        return isRegistered;
    }

    @Override
    public IMessageBeanDescriptor getMessageBeanDescriptor(INamespace namespace)
    {
        Assert.parameterNotNull(namespace, "Parameter 'namespace' must not be null");

        IMessageBeanDescriptor descriptor = null;

        QueryRunner run = new QueryRunner();
        Connection conn = null;
        try
        {
            conn = DriverManager.getConnection(JDBC_APACHE_COMMONS_DBCP_URL);

            ResultSetHandler<MessageBeanTable> mappedMessageBeanHandler = new BeanHandler<MessageBeanTable>(MessageBeanTable.class);

            if (logger.isLoggable(Level.FINE))
            {
                logger.fine("Looking for " + namespace.toString());
            }

            //
            // Message Bean
            //
            MessageBeanTable mappedMessageBean = run.query(conn,
                    QUERY_MAPPED_BEAN,
                    mappedMessageBeanHandler,
                    namespace.getFamily(),
                    namespace.getId());

            MessageBeanBuilder builder = new MessageBeanBuilder(mappedMessageBean.getMessageFamily(),
                    mappedMessageBean.getMessageId(),
                    mappedMessageBean.getMessageRefId());

            builder.setDescription(mappedMessageBean.getDescription());

            //
            // Message Bean Properties
            //
            ResultSetHandler<List<MessageBeanPropertyTable>> mappedMessageBeanPropertyHandler
                    = new BeanListHandler<MessageBeanPropertyTable>(MessageBeanPropertyTable.class);

            List<MessageBeanPropertyTable> properties = run.query(conn,
                    QUERY_MAPPED_BEAN_PROPERTIES,
                    mappedMessageBeanPropertyHandler,
                    mappedMessageBean.getBeanId());

            for (MessageBeanPropertyTable property : properties)
            {
                builder.mapProperty(property.getPropertyName(),
                        property.getDataType(),
                        property.getLength(),
                        property.getPrecision(),
                        property.isPrimaryKeyMember(),
                        property.isNullable())
                        .setPropertyDescription(property.getPropertyName(), property.getDescription());
            }

            //
            // Message Bean Metadata
            //
            ResultSetHandler<List<MessageBeanMetadataTable>> mappedMessageBeanMetadataHandler
                    = new BeanListHandler<MessageBeanMetadataTable>(MessageBeanMetadataTable.class);

            List<MessageBeanMetadataTable> metadata = run.query(conn,
                    QUERY_MAPPED_BEAN_METADATA,
                    mappedMessageBeanMetadataHandler,
                    mappedMessageBean.getBeanId());

            for (MessageBeanMetadataTable key : metadata)
            {
                builder.addMetadata(key.getPropertyName(), key.getPropertyValue());
            }

            //
            // Build the Descriptor
            descriptor = builder.build();

        } catch (SQLException e)
        {
            logger.severe(e.getMessage());
            e.printStackTrace();

            Exception nestedException = e.getNextException();
            if (nestedException != null)
            {
                logger.severe(nestedException.getMessage());
                nestedException.printStackTrace();
            }

        } finally
        {
            try
            {
                DbUtils.close(conn);
            } catch (SQLException e)
            {
                e.printStackTrace();
            }
        }

        return descriptor;
    }

    @Override
    public <T> long saveMessage(Class<T> messageBeanClass, T message)
    {
        if (!projectManager.isProjectOpen())
        {
            throw new IllegalStateException("Message persistence is only valid within the context of an open project");
        }

        INamespace namespace = new Namespace(messageBeanClass);
        IMessageBeanDescriptor descriptor = getMessageBeanDescriptor(namespace);

        long assignedKey = -1;

        if (null != descriptor)
        {
            boolean hasIdentityColumn = false;
            String identityColumnProperty = null;

            StringBuilder paramsBuf = new StringBuilder("(");
            StringBuilder sqlBuf = new StringBuilder("INSERT INTO ");

            //
            // Build the INSERT INTO statement with anti-SQL-injection params, ?
            //
            sqlBuf.append(extractTableName(descriptor))
                    .append(" (");

            int fieldCount = 0;
            Iterator<IMessageBeanProperty> propNamesIter = descriptor.properties().iterator();
            while (propNamesIter.hasNext())
            {
                IMessageBeanProperty beanProperty = propNamesIter.next();

                if (DataType.TransientType == beanProperty.getDataType())
                {
                    continue;
                }


                if (beanProperty.getDataType() != DataType.IdentityType && beanProperty.getDataType() != DataType.TransientType)
                {
                    if (fieldCount > 0)
                    {
                        sqlBuf.append(", ");
                        paramsBuf.append(", ");
                    }

                    sqlBuf.append(beanProperty.getId());

                    if (beanProperty.getDataType() != DataType.XmlType)
                    {
                        paramsBuf.append("?");

                    } else
                    {
                        paramsBuf.append("XMLPARSE (DOCUMENT CAST (? AS CLOB) PRESERVE WHITESPACE)");
                    }

                    fieldCount++;

                } else
                {
                    identityColumnProperty = beanProperty.getId();
                    hasIdentityColumn = true;
                }
            }
            sqlBuf.append(") VALUES ")
                    .append(paramsBuf.toString())
                    .append(")");

            logger.config(sqlBuf.toString());


            //
            // Extract the actual values to pass into the PreparedStatement
            //
            PropertyDescriptor[] propertyDescriptors = BeanUtil.propertyDescriptors(messageBeanClass);
            if (null != propertyDescriptors)
            {
                // The order returned from introspection likely won't match the order of our registration
                Map<String, Method> methodMap = new HashMap<String, Method>();

                for (PropertyDescriptor propertyDescriptor : propertyDescriptors)
                {
                    methodMap.put(propertyDescriptor.getName(), propertyDescriptor.getReadMethod());
                }


                Connection conn = null;
                PreparedStatement pstmt = null;

                try
                {
                    conn = DriverManager.getConnection(JDBC_APACHE_COMMONS_DBCP_URL);

                    if (logger.isLoggable(Level.FINE))
                    {
                        logger.fine("hasIdentityColumn = " + hasIdentityColumn);
                    }

                    if (hasIdentityColumn)
                    {
                        pstmt = conn.prepareStatement(sqlBuf.toString(), Statement.RETURN_GENERATED_KEYS);
                    } else
                    {
                        pstmt = conn.prepareStatement(sqlBuf.toString());
                    }

                    int position = 1;

                    IDerivativeConverter<PreparedStatement> derivativeConverter
                            = DerivativeFactory.createDerivativeConverter(PreparedStatement.class);

                    for (IMessageBeanProperty beanProperty : descriptor.properties())
                    {
                        if (beanProperty.getDataType() != DataType.IdentityType && beanProperty.getDataType() != DataType.TransientType)
                        {
                            Object value = null;

                            try
                            {

                                Method readMethod = methodMap.get(beanProperty.getId());
                                if (null != readMethod)
                                {
                                    value = readMethod.invoke(message);
                                } else
                                {
                                    throw new IllegalStateException("Incorrectly mapped property '" + beanProperty.getId() + "': please fix the property name in the IMessageBeanDescriptor registration");
                                }
                            } catch (IllegalAccessException e)
                            {
                                e.printStackTrace();
                            } catch (InvocationTargetException e)
                            {
                                e.printStackTrace();
                            }

                            derivativeConverter.convert(pstmt, beanProperty.getDataType(), value, position++);
                        }
                    }

                    pstmt.executeUpdate();

                    if (hasIdentityColumn)
                    {
                        ResultSet rs = pstmt.getGeneratedKeys();

                        if (rs.next())
                        {
                            assignedKey = rs.getLong(1);

                            if (logger.isLoggable(Level.FINE))
                            {
                                logger.fine("DB Assigned Identity: " + assignedKey);
                            }

                            //
                            // Push the identity value back into the bean itself
                            //
                            for (PropertyDescriptor propertyDescriptor : propertyDescriptors)
                            {
                                if (propertyDescriptor.getName().equals(identityColumnProperty))
                                {
                                    Method setMethod = propertyDescriptor.getWriteMethod();
                                    setMethod.invoke(message, assignedKey);
                                    break;
                                }
                            }
                        }
                    }

                } catch (IllegalStateException e)
                {
                    throw e;
                } catch (Exception e)
                {
                    logger.severe("Unable to execute INSERT INTO script: " + e.getMessage());
                    e.printStackTrace();
                } finally
                {
                    try
                    {
                        DbUtils.close(pstmt);
                        DbUtils.close(conn);
                    } catch (SQLException e)
                    {
                        e.printStackTrace();
                    }
                }

            } else
            {
                final String msg = "Unable to persist message bean " + messageBeanClass.getCanonicalName() + "; bean introspection failed";
                logger.severe(msg);
                throw new IllegalStateException(msg);
            }

        } else
        {
            final String msg = "Attempt to save a message bean that wasn't registered: " + messageBeanClass.getCanonicalName();
            logger.severe(msg);
            throw new IllegalArgumentException(msg);
        }

        return assignedKey;
    }

    @Override
    public <T> boolean updateMessage(Class<T> messageBeanClass, T message, IMessageBeanProperty[] properties)
    {
        if (!projectManager.isProjectOpen())
        {
            throw new IllegalStateException("Message persistence is only valid within the context of an open project");
        }

        if (properties.length == 0)
        {
            throw new IllegalStateException("At least one IMessageBeanProperty must be specified for update");
        }

        Map<String, IMessageBeanProperty> propertyMap = new HashMap<String, IMessageBeanProperty>();

        for (IMessageBeanProperty property : properties)
        {
            if (null != property)
            {
                propertyMap.put(property.getId(), property);
            } else
            {
                logger.warning("Detecting a null IMessageBeanProperty in the property listing; ignoring null");
            }
        }

        int affectedRowCount = 0;

        INamespace namespace = new Namespace(messageBeanClass);
        IMessageBeanDescriptor descriptor = getMessageBeanDescriptor(namespace);

        if (null != descriptor)
        {
            final String tableName = extractTableName(descriptor);

            if (descriptor.hasIdentityColumn())
            {

                PropertyDescriptor[] propertyDescriptors = BeanUtil.propertyDescriptors(messageBeanClass);

                String[] identityColumns = descriptor.getIdentityColumn();
                if (identityColumns.length > 0)
                {
                    String idColumnName = identityColumns[0];

                    QueryRunner run = new QueryRunner();
                    Connection conn = null;
                    try
                    {
                        conn = DriverManager.getConnection(JDBC_APACHE_COMMONS_DBCP_URL);

                        try
                        {
                            Object[] values = new Object[properties.length + 1];

                            int paramCount = 0;
                            StringBuilder sql = new StringBuilder("UPDATE ")
                                    .append(tableName)
                                    .append(" SET ");

                            StringBuilder whereClause = new StringBuilder(" WHERE ");

                            for (PropertyDescriptor propertyDescriptor : propertyDescriptors)
                            {
                                if (propertyDescriptor.getName().equals(idColumnName))
                                {
                                    Method readMethod = propertyDescriptor.getReadMethod();
                                    values[properties.length] = readMethod.invoke(message);
                                    whereClause.append(idColumnName).append(" = ?");
                                } else if (propertyMap.containsKey(propertyDescriptor.getName()))
                                {
                                    if (paramCount > 0)
                                    {
                                        sql.append(", ");
                                    }

                                    Method readMethod = propertyDescriptor.getReadMethod();
                                    values[paramCount] = readMethod.invoke(message);

                                    sql.append(propertyDescriptor.getName()).append(" = ? ");
                                    paramCount++;
                                }
                            }

                            sql.append(whereClause.toString());

                            logger.config(sql.toString());

                            if (paramCount != properties.length)
                            {
                                throw new IllegalStateException("Mismatch between declared IMessageBeanProperty and SQL UPDATE parameters");
                            }

                            affectedRowCount = run.update(conn, sql.toString(), values);
                        } catch (IllegalAccessException e)
                        {
                            e.printStackTrace();
                        } catch (InvocationTargetException e)
                        {
                            e.printStackTrace();
                        }


                    } catch (SQLException e)
                    {
                        logger.severe(e.getMessage());
                        e.printStackTrace();

                        Exception nestedException = e.getNextException();
                        if (nestedException != null)
                        {
                            logger.severe(nestedException.getMessage());
                            nestedException.printStackTrace();
                        }

                    } finally
                    {
                        try
                        {
                            DbUtils.close(conn);
                        } catch (SQLException e)
                        {
                            e.printStackTrace();
                        }
                    }
                }
            } else
            {
                throw new UnsupportedOperationException("DefaultMessageBeanManager.updateMessage() only supports updates against message beans with an identity column");
            }
        }

        return (affectedRowCount > 0);
    }

    @Override
    public boolean supportsUpdates()
    {
        // TODO: Define a configuration property prototype to allow the app config to disable update capability
        return true;
    }

    @Override
    public boolean supportsDeletes()
    {
        // TODO: Define a configuration property prototype to allow the app config to disable deletion capability
        return true;
    }

    @Override
    public <T> boolean deleteMessage(Class<T> messageBeanClass, T message)
    {
        if (!projectManager.isProjectOpen())
        {
            throw new IllegalStateException("Message persistence is only valid within the context of an open project");
        }

        int affectedRowCount = 0;

        INamespace namespace = new Namespace(messageBeanClass);
        IMessageBeanDescriptor descriptor = getMessageBeanDescriptor(namespace);

        if (null != descriptor)
        {
            final String tableName = extractTableName(descriptor);

            if (descriptor.hasIdentityColumn())
            {

                PropertyDescriptor[] propertyDescriptors = BeanUtil.propertyDescriptors(messageBeanClass);

                String[] identityColumns = descriptor.getIdentityColumn();
                if (identityColumns.length > 0)
                {
                    String idColumnName = identityColumns[0];

                    QueryRunner run = new QueryRunner();
                    Connection conn = null;
                    try
                    {
                        conn = DriverManager.getConnection(JDBC_APACHE_COMMONS_DBCP_URL);


                        try
                        {
                            for (PropertyDescriptor propertyDescriptor : propertyDescriptors)
                            {
                                if (propertyDescriptor.getName().equals(idColumnName))
                                {
                                    Method readMethod = propertyDescriptor.getReadMethod();
                                    Object value = readMethod.invoke(message);

                                    StringBuilder sql = new StringBuilder("DELETE FROM ")
                                            .append(tableName)
                                            .append(" WHERE ")
                                            .append(idColumnName)
                                            .append(" = ?");

                                    logger.config(sql.toString());

                                    affectedRowCount = run.update(conn, sql.toString(), value);
                                }
                            }
                        } catch (IllegalAccessException e)
                        {
                            e.printStackTrace();
                        } catch (InvocationTargetException e)
                        {
                            e.printStackTrace();
                        }


                    } catch (SQLException e)
                    {
                        logger.severe(e.getMessage());
                        e.printStackTrace();

                        Exception nestedException = e.getNextException();
                        if (nestedException != null)
                        {
                            logger.severe(nestedException.getMessage());
                            nestedException.printStackTrace();
                        }

                    } finally
                    {
                        try
                        {
                            DbUtils.close(conn);
                        } catch (SQLException e)
                        {
                            e.printStackTrace();
                        }
                    }
                }
            } else
            {
                throw new UnsupportedOperationException("DefaultMessageBeanManager.deleteMessage() only supports deletion against message beans with an identity column");
            }
        }

        return (affectedRowCount > 0);
    }

    @Override
    public <T> List<T> listMessages(Class<T> messageBeanClass)
    {
        if (!projectManager.isProjectOpen())
        {
            throw new IllegalStateException("Message persistence is only valid within the context of an open project");
        }

        List<T> list = new ArrayList<T>();

        INamespace namespace = new Namespace(messageBeanClass);
        IMessageBeanDescriptor descriptor = getMessageBeanDescriptor(namespace);

        if (null != descriptor)
        {
            final String tableName = extractTableName(descriptor);

            QueryRunner run = new QueryRunner();
            Connection conn = null;
            try
            {
                conn = DriverManager.getConnection(JDBC_APACHE_COMMONS_DBCP_URL);

                ResultSetHandler<List<T>> mappedMessageBeanHandler = new BeanListHandler<T>(messageBeanClass, new ProteusRowProcessor(this));

                StringBuilder sql = new StringBuilder("SELECT ");

                int selectCount = 0;
                Iterator<IMessageBeanProperty> iter = descriptor.properties().iterator();
                while (iter.hasNext())
                {
                    IMessageBeanProperty property = iter.next();

                    if (DataType.TransientType == property.getDataType())
                    {
                        continue;
                    }

                    if (selectCount > 0)
                    {
                        sql.append(", ");
                    }

                    if (property.getDataType() == DataType.XmlType)
                    {
                        sql.append("XMLSERIALIZE (")
                                .append(property.getId())
                                .append(" as CLOB) as ")
                                .append(property.getId());

                    } else
                    {
                        sql.append(property.getId());
                    }

                    selectCount++;

                }

                sql.append(" FROM ").append(tableName);

                logger.config(sql.toString());

                List<T> results = run.query(conn, sql.toString(), mappedMessageBeanHandler);

                if (null != results)
                {
                    list.addAll(results);
                }

            } catch (SQLException e)
            {
                logger.severe(e.getMessage());
                e.printStackTrace();

                Exception nestedException = e.getNextException();
                if (nestedException != null)
                {
                    logger.severe(nestedException.getMessage());
                    nestedException.printStackTrace();
                }

            } finally
            {
                try
                {
                    DbUtils.close(conn);
                } catch (SQLException e)
                {
                    e.printStackTrace();
                }
            }
        } else
        {
            final String msg = "Unable to retrieve a message bean descriptor for bean class " + messageBeanClass.getCanonicalName();
            logger.severe(msg);
            throw new IllegalStateException(msg);
        }

        return list;
    }

    @Override
    public <T> Iterable<T> createIterator(Class<T> messageBeanClass)
    {
        if (!projectManager.isProjectOpen())
        {
            throw new IllegalStateException("Message persistence is only valid within the context of an open project");
        }

        final Iterator<T> iterator = listMessages(messageBeanClass).iterator();

        return new Iterable<T>()
        {
            @Override
            public Iterator<T> iterator()
            {
                return iterator;
            }
        };
    }

    @Override
    public boolean supportsParameterizedQueries()
    {
        return true;
    }

    @Override
    public <T> IResultSet<T> executeQuery(Class<T> messageBeanClass, String query) throws IllegalExpressionException
    {
        if (!projectManager.isProjectOpen())
        {
            throw new IllegalStateException("Message persistence is only valid within the context of an open project");
        }

        Assert.parameterNotNull(query, "Parameter 'query' must not be null");

        ResultSetWrapper<T> resultSet = null;

        INamespace namespace = new Namespace(messageBeanClass);
        IMessageBeanDescriptor descriptor = getMessageBeanDescriptor(namespace);

        IGrammarInspector inspector = createGrammarInspector(descriptor);
        inspector.inspect(query);

        if (inspector.getParameterCount() > 0)
        {
            throw new IllegalArgumentException("Query filter defines parameters via ? token; invoke executeQuery() that accepts parameters instead");
        }

        String tableName = extractTableName(descriptor);

        QueryRunner run = new QueryRunner();
        Connection conn = null;
        try
        {
            conn = DriverManager.getConnection(JDBC_APACHE_COMMONS_DBCP_URL);


            ResultSetHandler<List<T>> mappedMessageBeanHandler = new BeanListHandler<T>(messageBeanClass, new ProteusRowProcessor(this));

            StringBuilder sql = new StringBuilder("SELECT ");

            Iterator<IMessageBeanProperty> iter = descriptor.properties().iterator();
            while (iter.hasNext())
            {
                IMessageBeanProperty property = iter.next();

                if (property.getDataType() == DataType.XmlType)
                {
                    sql.append("XMLSERIALIZE (")
                            .append(property.getId())
                            .append(" as CLOB) as ")
                            .append(property.getId());

                } else
                {
                    if(property.getDataType() != DataType.TransientType)
                    {
                        sql.append(property.getId());
                    }
                }

                if (iter.hasNext() && property.getDataType() != DataType.TransientType)
                {
                    sql.append(", ");
                }
            }

            sql.append(" FROM ").append(tableName);

            if (query.length() > 0)
            {
                sql.append(" WHERE ").append(query);
            }

            logger.config(sql.toString());

            List<T> results = run.query(conn, sql.toString(), mappedMessageBeanHandler);

            if (null != results)
            {
                resultSet = new ResultSetWrapper<T>(results);
            }

        } catch (SQLException e)
        {
            logger.severe(e.getMessage());
            e.printStackTrace();

            Exception nestedException = e.getNextException();
            if (nestedException != null)
            {
                logger.severe(nestedException.getMessage());
                nestedException.printStackTrace();
            }

        } finally
        {
            try
            {
                DbUtils.close(conn);
            } catch (SQLException e)
            {
                e.printStackTrace();
            }
        }

        if (null == resultSet)
        {
            resultSet = new ResultSetWrapper<T>(new ArrayList<T>());
        }

        return resultSet;
    }

    @Override
    public <T> IResultSet<T> executeQuery(Class<T> messageBeanClass, String query, Object... params) throws IllegalExpressionException
    {
        if (!projectManager.isProjectOpen())
        {
            throw new IllegalStateException("Message persistence is only valid within the context of an open project");
        }

        Assert.parameterNotNull(query, "Parameter 'query' must not be null");

        ResultSetWrapper<T> resultSet = null;

        INamespace namespace = new Namespace(messageBeanClass);
        IMessageBeanDescriptor descriptor = getMessageBeanDescriptor(namespace);

        IGrammarInspector inspector = createGrammarInspector(descriptor);
        inspector.inspect(query);

        Assert.equals(inspector.getParameterCount(), params.length, "Count mismatch between query and params");

        String tableName = extractTableName(descriptor);

        QueryRunner run = new QueryRunner();
        Connection conn = null;
        try
        {
            conn = DriverManager.getConnection(JDBC_APACHE_COMMONS_DBCP_URL);


            ResultSetHandler<List<T>> mappedMessageBeanHandler = new BeanListHandler<T>(messageBeanClass, new ProteusRowProcessor(this));

            StringBuilder sql = new StringBuilder("SELECT ");

            Iterator<IMessageBeanProperty> iter = descriptor.properties().iterator();
            while (iter.hasNext())
            {
                IMessageBeanProperty property = iter.next();

                if (property.getDataType() == DataType.XmlType)
                {
                    sql.append("XMLSERIALIZE (")
                            .append(property.getId())
                            .append(" as CLOB) as ")
                            .append(property.getId());

                } else
                {
                    if(property.getDataType() != DataType.TransientType)
                    {
                        sql.append(property.getId());
                    }
                }

                if (iter.hasNext() && property.getDataType() != DataType.TransientType)
                {
                    sql.append(", ");
                }
            }

            sql.append(" FROM ").append(tableName);

            if (query.length() > 0)
            {
                sql.append(" WHERE ").append(query);
            }

            logger.config(sql.toString());

            List<T> results = run.query(conn, sql.toString(), mappedMessageBeanHandler, params);

            if (null != results)
            {
                resultSet = new ResultSetWrapper<T>(results);
            }

        } catch (SQLException e)
        {
            logger.severe(e.getMessage());
            e.printStackTrace();

            Exception nestedException = e.getNextException();
            if (nestedException != null)
            {
                logger.severe(nestedException.getMessage());
                nestedException.printStackTrace();
            }

        } finally
        {
            try
            {
                DbUtils.close(conn);
            } catch (SQLException e)
            {
                e.printStackTrace();
            }
        }

        if (null == resultSet)
        {
            resultSet = new ResultSetWrapper<T>(new ArrayList<T>());
        }

        return resultSet;
    }

    protected void shutdownDriver() throws SQLException
    {
        PoolingDriver driver = (PoolingDriver) DriverManager.getDriver(JDBC_APACHE_COMMONS_DBCP_URL);
        driver.closePool(DEFAULT_PERSISTENCE);
    }

    protected void openProjectDataStore(String name) throws ServiceInitializationException
    {
        try
        {
            String projectsDirectoryPath = getAppConfigService().getDefaultProjectsDirectory();

            String url = "jdbc:derby:" + projectsDirectoryPath + "/" + name + "/messagestore;create=true";
            logger.config("Creating new project named " + name + " with JDBC URL " + url);
            setupDriver(url);
        } catch (Exception ex)
        {
            ex.printStackTrace();
            throw new ServiceInitializationException("Unexpected exception during database initialization", ex);
        }
    }

    protected void setupDriver(String connectURI) throws ClassNotFoundException, SQLException
    {
        GenericObjectPool gPool = new GenericObjectPool();

        String jdbcDriverClass = getAppConfigService().getMetadata().getProperty(KEY_JDBC_DRIVER);

        if (null == jdbcDriverClass)
        {
            logger.config("No JDBC Driver Class specified in IAppConfigService; defaulting to embedded Derby");
            jdbcDriverClass = "org.apache.derby.jdbc.EmbeddedDriver";
        } else
        {
            logger.config("Attempting to load driver class " + jdbcDriverClass);
        }

        DbUtils.loadDriver(jdbcDriverClass);

        DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory(connectURI, null);

        KeyedObjectPoolFactory kopf = new GenericKeyedObjectPoolFactory(null, 10);

        // Per the DBCP docs, we need a PCF, but we never need to reference it- construction injects it into the pool
        new PoolableConnectionFactory(cf, gPool, kopf, null, false, true);

        for (int i = 0; i < 15; i++)
        {
            try
            {
                gPool.addObject();
            } catch (Exception e)
            {
                e.printStackTrace();
            }
        }

        PoolingDriver pd = new PoolingDriver();
        pd.registerPool(DEFAULT_PERSISTENCE, gPool);

        for (int i = 0; i < 15; i++)
        {
            try
            {
                gPool.addObject();
            } catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void accept(IMessageBeanDescriptorVisitor visitor) throws IOException
    {
        if (projectManager.isProjectOpen())
        {
            visitor.visit(this);
        } else
        {
            throw new IllegalStateException("Unable to accept IMessageBeanDescriptorVisitor as no project is presently open");
        }
    }
}
